Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce assorted Reactor StepVerifier Refaster rules #1132

Merged
merged 9 commits into from
Nov 11, 2024

Conversation

werli
Copy link
Member

@werli werli commented Apr 11, 2024

Summary

This PR introduces a bunch of rules associated to StepVerifier.Asserations and a trivial StepVerifier rule.


Example

I spotted the following interesting pattern:

Mono.empty()
    .as(StepVerifier::create)
    .expectError(SpecificException.class)
    .verifyThenAssertThat()
    .hasOperatorErrorWithMessage("msg");

Which made me find the interesting StepVerifier#Assertions API. It's quite low-level, and IMO not as straightforward to read and write.

The example itself is most often represented in our code-base using AssertJ's richer API:

Mono.empty()
    .as(StepVerifier::create)
    .verifyErrorSatisfies(t ->
        assertThat(t)
        .isInstanceOf(SpecificException.class)
        .hasMessage("msg"));

Suggested commit message:

Introduce assorted Reactor `StepVerifier` Refaster rules (#1132)

Copy link

Looks good. No mutations were possible for these changes.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

Comment on lines 1948 to 1975
/**
* Prefer {@link StepVerifier.LastStep#verifyErrorSatisfies(Consumer)} with AssertJ over more
* contrived alternatives.
*/
static final class StepVerifierLastStepVerifyErrorSatisfiesAssertJ {
@BeforeTemplate
void before(StepVerifier.LastStep step, Class<? extends Throwable> clazz, String message) {
Refaster.anyOf(
step.expectError()
.verifyThenAssertThat()
.hasOperatorErrorOfType(clazz)
.hasOperatorErrorWithMessage(message),
step.expectError(clazz).verifyThenAssertThat().hasOperatorErrorWithMessage(message),
step.expectErrorMessage(message).verifyThenAssertThat().hasOperatorErrorOfType(clazz));
}

@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(StepVerifier.LastStep step, Class<? extends Throwable> clazz, String message) {
step.verifyErrorSatisfies(
t -> Assertions.assertThat(t).isInstanceOf(clazz).hasMessage(message));
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Asserting both class and message blocks one to use the recommended StepVerifier.Step#verifyError(Class) or StepVerifier.Step#verifyErrorMessage(String). Choosing an alternative isn't easy since there are many possibilities. Out of all the options, AssertJ's assertions have the richest API, so I'd recommend this.

The StepVerifierVerify rule introduced in this PR aims to rewrite the use-cases in which only one of class or message are asserted by dropping the StepVerifier.Assertions type which allows other rules to rewrite further.

Comment on lines 1916 to 1935
/**
* Prefer {@link StepVerifier.LastStep#verifyErrorMatches(Predicate)} over more verbose
* alternatives.
*/
static final class StepVerifierLastStepVerifyErrorMatchesAssertions {
@BeforeTemplate
void before(StepVerifier.LastStep step, Predicate<Throwable> predicate) {
step.expectError().verifyThenAssertThat().hasOperatorErrorMatching(predicate);
}

@AfterTemplate
void after(StepVerifier.LastStep step, Predicate<Throwable> predicate) {
step.verifyErrorMatches(predicate);
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This case is not exhaustive due to the overload of StepVerifier.LastStep#expectedError, but IMO covers sufficiently.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can collapse this one into the preceding method.

Comment on lines 1797 to 1823
/** Don't unnecessarily invoke {@link StepVerifier#verifyLater()} multiple times. */
static final class StepVerifierVerifyLater<T> {
@BeforeTemplate
StepVerifier before(StepVerifier stepVerifier) {
return stepVerifier.verifyLater().verifyLater();
}

@AfterTemplate
StepVerifier after(StepVerifier stepVerifier) {
return stepVerifier.verifyLater();
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API spec mentions this is a noop. I didn't fully understand why this API may be useful for in the first place.

@werli werli force-pushed the werli/reactor-verify-error-satisfies branch from 7395f93 to 0eea3ac Compare April 11, 2024 21:13
Copy link

Looks good. No mutations were possible for these changes.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

1 similar comment
Copy link

Looks good. No mutations were possible for these changes.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@Stephan202 Stephan202 added this to the 0.17.0 milestone Apr 12, 2024
Copy link
Member

@Stephan202 Stephan202 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a commit. Tnx @werli!

Comment on lines 1916 to 1935
/**
* Prefer {@link StepVerifier.LastStep#verifyErrorMatches(Predicate)} over more verbose
* alternatives.
*/
static final class StepVerifierLastStepVerifyErrorMatchesAssertions {
@BeforeTemplate
void before(StepVerifier.LastStep step, Predicate<Throwable> predicate) {
step.expectError().verifyThenAssertThat().hasOperatorErrorMatching(predicate);
}

@AfterTemplate
void after(StepVerifier.LastStep step, Predicate<Throwable> predicate) {
step.verifyErrorMatches(predicate);
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can collapse this one into the preceding method.

Comment on lines 667 to 682
Mono.empty()
.as(StepVerifier::create)
.expectError()
.verifyThenAssertThat()
.hasOperatorErrorOfType(IllegalStateException.class)
.hasOperatorErrorWithMessage("foo");
Mono.empty()
.as(StepVerifier::create)
.expectError(IllegalStateException.class)
.verifyThenAssertThat()
.hasOperatorErrorWithMessage("bar");
Mono.empty()
.as(StepVerifier::create)
.expectErrorMessage("baz")
.verifyThenAssertThat()
.hasOperatorErrorOfType(IllegalStateException.class);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use three different Throwable types like a bit further up.

*/
static final class StepVerifierVerify {
@BeforeTemplate
void before(StepVerifier stepVerifier) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While in this case arguably incorrect, we do generally match any expression, even if that can lead to compilation errors. (At least this will force the user to clean up the code.)

I'll apply those changes to this PR, though I could also see us change our mind on that in the future.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My intent was indeed to avoid breaking code changes by only matching the "dangling" case in which the created StepVerifier.Assertions isn't used or cases we should clearly rewrite.

StepVerifier#verifyThenAssertThat has some valid yet niche usages, see internal usages. Sure, we could suppress the warning here, but IMO the warning would be a false positive. WDYT?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay - I've given this another thought. Let's proceed as-is, and violations could be suppressed through the warning. The internal use cases are quite sparse.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed offline: I do think @werli has a good point. Ideally we have our cake and eat it too (i.e., have separate "aggressive" and "safe" modes). I'll add a comment describing the current thinking of what that could look like.

Copy link

Looks good. No mutations were possible for these changes.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

Copy link
Member

@rickie rickie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice improvements!

@rickie rickie force-pushed the werli/reactor-verify-error-satisfies branch from 3742218 to fcbda80 Compare May 2, 2024 06:07
Copy link

github-actions bot commented May 2, 2024

Looks good. No mutations were possible for these changes.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

Copy link

sonarqubecloud bot commented May 2, 2024

Quality Gate Passed Quality Gate passed

Issues
0 New issues
0 Accepted issues

Measures
0 Security Hotspots
No data about Coverage
0.0% Duplication on New Code

See analysis details on SonarCloud

@Stephan202 Stephan202 modified the milestones: 0.17.0, 0.18.0 Jul 20, 2024
@Stephan202 Stephan202 modified the milestones: 0.18.0, 0.19.0 Aug 11, 2024
@rickie rickie modified the milestones: 0.19.0, 0.20.0 Oct 25, 2024
@werli werli force-pushed the werli/reactor-verify-error-satisfies branch from fcbda80 to 8f803e0 Compare November 1, 2024 12:04
@werli
Copy link
Member Author

werli commented Nov 1, 2024

^ Rebased to resolve conflicts. IMO, we can proceed. 👍

Copy link

github-actions bot commented Nov 1, 2024

Looks good. No mutations were possible for these changes.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

1 similar comment
Copy link

github-actions bot commented Nov 1, 2024

Looks good. No mutations were possible for these changes.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@werli werli requested a review from Stephan202 November 1, 2024 13:11
Copy link
Member

@Stephan202 Stephan202 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a commit, summarizing what we discussed offline. Let's merge after #1387 is merged and released.

Copy link

github-actions bot commented Nov 3, 2024

Looks good. No mutations were possible for these changes.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@Stephan202
Copy link
Member

The build fails due to what appears to be a bug in JDK 23. Will require further investigation later ~today.

@Stephan202
Copy link
Member

The build fails due to what appears to be a bug in JDK 23. Will require further investigation later ~today.

Will require attaching a debugger, I think, for which I'll need to find more time. Observations so far:

  • The errors happen for {@link StepVerifier.LastStep#verify()} and {@link StepVerifier.LastStep#verify(Duration)}.
  • There are no errors for {@link StepVerifier.LastStep#verifyError()} and {@link StepVerifier.LastStep#verifyError(Class)}. (This is surprising: same (nested) class, also a pair of overloads.)
  • As expected, the issue doesn't trigger with -Xdoclint:-reference, as the relevant validation is then skipped altogether.
  • Based on a quick test with 22.0.1-graalce (which I happened to still have installed locally) the issue isn't present in JDK 22.

Copy link
Member Author

@werli werli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last changes LGTM, thanks @Stephan202 💪
Interesting bug investigation. What do we do in this scenario for error-prone-support? Change our code & report, or report & leave the PR open until it's fixed? The latter may take a while.

@Stephan202
Copy link
Member

Interesting bug investigation. What do we do in this scenario for error-prone-support? Change our code & report, or report & leave the PR open until it's fixed? The latter may take a while.

Discussed offline: certainly we'll want to proceed with this PR. Whatever workaround we go for, ideally we do link it to a bug report. This would likely entail the following steps:

  1. Use a debugger to better understand and be able to describe the issue and, if possible, create a minimal reproducible example.
  2. Create an account to file a JDK bug ticket.
  3. File a bug ticket.

Depending on availability one of us will have a crack at this.

@Stephan202 Stephan202 force-pushed the werli/reactor-verify-error-satisfies branch from f7b1121 to cee4b2c Compare November 11, 2024 12:41
@Stephan202
Copy link
Member

Alright, I rebased and added a commit. Turns out the bug is in all versions preceding JDK 23: There are no StepVerifier.LastStep#verify overloads: #verify is defined on StepVerifier instead. 😅. Will merge once built.

Copy link

Looks good. No mutations were possible for these changes.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

Copy link

@Stephan202 Stephan202 merged commit da9b313 into master Nov 11, 2024
16 checks passed
@Stephan202 Stephan202 deleted the werli/reactor-verify-error-satisfies branch November 11, 2024 13:08
@werli
Copy link
Member Author

werli commented Nov 11, 2024

💡💡💡 Thanks @Stephan202 💪

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

4 participants